
from matplotlib.pyplot import close, plot
import pandas as pd
import backtrader as bt
from backtrader_plotting import Bokeh
from backtrader_plotting.schemes import Tradimo
from my_plot import *
from py_mysql import *
import numpy as np
import talib as ta
import math

class PandasData_more(bt.feeds.PandasData):
    pass
    # lines = ('RSI15','AD','MFI',) # 要添加的线
    # # 设置 line 在数据源上的列位置
    # params=(
    #     ('RSI15', -1),
    #     ('AD', -1),
    #     ('MFI', -1),
    #        ) 


# 创建策略继承bt.strategy
class TestStrategy(bt.Strategy):
    params = (
        ('maperiod', 15),
        # 判断是否输出该日志
        ('printlog', False),
    )

    # 初始化部分指标

    def __init__(self):
        self.dataclose = self.datas[0].close
        self.datavolume = self.datas[0].volume
        
        # 加入指标
        self.ema20 = bt.indicators.ExponentialMovingAverage(self.datas[0].close, period = 20)
        self.atr10 = bt.indicators.AverageTrueRange(period = 10, plot=False)

        self.maxClose = bt.indicators.Highest(self.datas[0].close, period=60,plot=False)
        self.minClose = bt.indicators.Lowest(self.datas[0].close, period=60,plot=False)
        

        # 下单数量
        self.volSize = 100
        
        # 止盈止损点
        self.topPrice = 0
        self.botPrice = 0

        # 止盈止损比例
        self.stopProfit = 1.382
        self.stopLoss = 0.618    # 0.618

        # 上穿状态值
        self.topSingal = 0
        # 下穿状态值
        self.botSingal = 0

        self.stdArr = []


    # 策略核心代码
    def next(self):
        # 当前日期
        self.time = str(bt.num2date(self.lines.datetime[0]))
        self.positionSize = self.position.size

        if len(self.stdArr)  < 30:
            self.stdArr.append(self.dataclose[0])
            return
        else:
            del(self.stdArr[0])
            self.stdArr.append(self.dataclose[0])

        # size75 = np.percentile(a,95)
        # a = np.array([self.stdArr])
        # std = a.std()


        topTrack = self.ema20[0] + 1.96*self.atr10[0]   #上轨
        midTrack = self.ema20[0]                        #中轨
        botTrack = self.ema20[0] - 1.96*self.atr10[0]   #下轨

        lastTopTrack = self.ema20[-1] + 1.96*self.atr10[-1]  #前一天上轨
        lastMidTrack = self.ema20[-1]                   #前一天中轨
        lastBotTrack = self.ema20[-1] - 1.96*self.atr10[-1]  #前一天下轨
        
        close = self.dataclose[0]
        lastClose = self.dataclose[-1]

        upSignal = lastClose < lastTopTrack and close > topTrack
        downSignal = lastClose > lastBotTrack and close < botTrack

        # adUpSingal = self.datas[0].AD[-1] < self.datas[0].AD[0] and lastClose < close
        # adDownSingal = self.datas[0].AD[-1] > self.datas[0].AD[0] and lastClose > close

        if not self.position:
            # 默认没有信号的时候
            if self.topSingal == 0:
                # 第一次上穿
                if upSignal:
                    self.topSingal = 1
                    return
            # 如果跌破中轨，从头开始
            if close < midTrack:
                self.topSingal = 0

            if self.topSingal == 1:
                # 第二次上穿
                if upSignal:

                    self.buy(size = self.volSize)
                    bad_size = close - self.quantile_p(self.stdArr,0.1)
                    self.topPrice = close + bad_size*self.stopProfit
                    self.botPrice = close - bad_size*self.stopLoss

                    self.topSingal = 0
                    return

            # 默认没有信号的时候
            if self.botSingal == 0:
                # 第一次下穿
                if downSignal:
                    self.botSingal = 1
                    return
            # 如果上穿中轨，从头开始
            if close > midTrack:
                self.botSingal = 0

            if self.botSingal == 1:
                # 第二次下穿
                if downSignal:
                    
                    self.sell(size = self.volSize)
                    bad_size = self.quantile_p(self.stdArr,0.9) - close
                    self.topPrice = close + bad_size*self.stopLoss
                    self.botPrice = close - bad_size*self.stopProfit

                    self.botSingal = 0
                    return

        # 有持仓
        else:
            if close > self.topPrice or close < self.botPrice:
                    print('触碰止盈止损,进行平仓')
                    self.close()
            if self.time == '2020-12-30 22:56:00':
                self.close()
            
            
    # 输出分位数
    def quantile_p(self,data, p):
        pos = (len(data) + 1)*p
        #pos = 1 + (len(data)-1)*p
        pos_integer = int(math.modf(pos)[1])
        pos_decimal = pos - pos_integer
        Q = data[pos_integer - 1] + (data[pos_integer] - data[pos_integer - 1])*pos_decimal
        return Q

    # 日志输出
    def log(self, txt, dt=None, doprint=False):
        if self.params.printlog or doprint:
            # 记录策略的执行日志
            dt = dt or self.datas[0].datetime.date(0)
            print(f'{dt.isoformat()},{txt}')

    # 订单状态通知，买入卖出都是下单
    def notify_order(self, order):
        # 提交了/接受了,  买/卖订单什么都不做
        if order.status in [order.Submitted, order.Accepted]:
            return
        # 检查一个订单是否完成
        # 注意:当资金不足时，broker会拒绝订单
        if order.status in [order.Completed]:
            if order.isbuy():
                print('日期：{}---买入价格:{}---买入手续费{}'.format(self.time,order.executed.price,
                      order.executed.comm))
                self.buyprice = order.executed.price
                self.buycomm = order.executed.comm
            elif order.issell():
                print('日期：{}---卖出价格:{}---卖出手续费{}'.format(self.time,order.executed.price,
                      order.executed.comm))
            self.bar_executed = len(self)
        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log('订单取消/保证金不足/拒绝', doprint=False)
        # 其他状态记录为：无法挂单
        self.order = None

    # 交易状态通知，一买一卖算交易（交易净利润）
    def notify_trade(self, trade):
        if not trade.isclosed:
            return

    # 策略结束时，多用于参数调优
    def stop(self):
        
        print(self.positionSize)
        self.log('(MA均线： %2d日) 期末总资金 %.2f' %
                 (self.params.maperiod, self.broker.getvalue()), doprint=False)


def main(startcash=1000000, com=0.0003, qts=100):

    # 创建主控制器
    cerebro = bt.Cerebro()
    # 导入策略参数寻优
    cerebro.addstrategy(TestStrategy)
    # cerebro.optstrategy(TestStrategy,maperiod=range(10, 15))
    # 获取数据
    df = writeExcl()  
    df.index = pd.to_datetime(df.date)
    df = df[['open', 'high', 'low', 'close', 'volume']]
    # 将数据加载至回测系统
    data = bt.feeds.PandasData(dataname=df)
    cerebro.adddata(data)

    # 获取数据
    # query_db = Mysql_search()
    # df = query_db.get_one(['ss'],'2020-06-01','2020-12-31')
    # # ZCL8  JM  J
    # for item in df:
    #     df2 = df[item]

    #     df2['RSI15'] = ta.RSI(df2.close, 𝑡𝑖𝑚𝑒𝑝𝑒𝑟𝑖𝑜𝑑 = 15)
    #     df2['AD'] = ta.AD(df2.high, df2.low, df2.close, df2.volume)
    #     df2['MFI'] = ta.MFI(df2.high,df2.low, df2.close,df2.volume, timeperiod = 14)
    #     # df2['plus'] = ta.PLUS_DI(df2.high, df2.low, df2.close ,14)
    #     # df2['rangema'] = ta.EMA(df2.range1, 80)


    #     # df2.to_csv('RSI5.csv',sep=',',index=True,header=True)
    #     print(df2)
    #     df2.index = pd.to_datetime( df2.index, utc= True)
    #     data = PandasData_more(
    #         dataname = df2,
    #         timeframe=bt.TimeFrame.Minutes)
    #     cerebro.adddata(data)

    # broker设置资金、手续费
    cerebro.broker.setcash(startcash)
    cerebro.broker.setcommission(commission=com)
    # 设置买入设置，策略，数量
    cerebro.addsizer(bt.sizers.FixedSize, stake=qts)
    # 以发出信号当日收盘价成交
    cerebro.broker.set_coc(True)

    print('期初总资金: %.2f' % cerebro.broker.getvalue())
    cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='_TimeReturn')
    result = cerebro.run()
    print('期末总资金: %.2f' % cerebro.broker.getvalue())

    # custom_plot(result)
    cerebro.plot()
    
    b = Bokeh(style='bar', tabs='multi', scheme=Tradimo())
    # cerebro.plot(b)

# def writeExcl():
#     df = pd.read_excel(r'D:\通达信\T0002\export2\IL8.xls')
#     df['code'] = 'IL8'
#     return df



def writeExcl():
    df = pd.read_excel(r'D:\Downloads\MAL8.xls')
    # df['code'] = 'MAL8'
    return df

main()
